/*
 * Copyright � 2002-2011 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

using System.Configuration;
using System.Reflection;
using System.Xml;
using NUnit.Framework;
using Spring.Objects;
using Spring.Proxy;
using Spring.Objects.Factory.Support;
using Spring.Util;
using ConfigurationException = System.Configuration.ConfigurationException;

namespace Spring.Context.Support;

/// <summary>
/// Unit tests for the ContextRegistry class.
/// </summary>
/// <author>Rick Evans</author>
[TestFixture]
public sealed class ContextRegistryTests
{
    [SetUp]
    public void SetUp()
    {
        ContextRegistry.Clear();
        ResetConfigurationSystem();
    }

    private static void ResetConfigurationSystem()
    {
        if (SystemUtils.MonoRuntime)
        {
            return;
        }

        FieldInfo initStateRef = typeof(ConfigurationManager).GetField("s_initState", BindingFlags.NonPublic | BindingFlags.Static);
        object notStarted = Activator.CreateInstance(initStateRef.FieldType);
        initStateRef.SetValue(null, notStarted);
    }

    /// <summary>
    /// This handler simulates an undefined configuration section
    /// </summary>
    private static object GetNullSection(object parent, object context, XmlNode section)
    {
        return null;
    }

    /// <summary>
    /// This handler simulates calls to ContextRegistry during context creation
    /// </summary>
    private static object GetContextRecursive(object parent, object context, XmlNode section)
    {
        return ContextRegistry.GetContext(); // this must fail!
    }

    [Test]
    public void ThrowsInvalidOperationExceptionOnRecursiveCallsToGetContext()
    {
        using (new HookableContextHandler.Guard(GetContextRecursive))
        {
            try
            {
                ContextRegistry.GetContext("somename");
                Assert.Fail("Should throw an exception");
            }
            catch (ConfigurationException ex)
            {
                InvalidOperationException rootCause = ex.GetBaseException() as InvalidOperationException;
                Assert.IsNotNull(rootCause);
                Assert.AreEqual("root context is currently in creation.", rootCause.Message.Substring(0, 38));
            }
        }
    }

    [Test]
    public void RegisterRootContext()
    {
        MockApplicationContext ctx = new MockApplicationContext();
        ContextRegistry.RegisterContext(ctx);
        IApplicationContext context = ContextRegistry.GetContext();
        Assert.IsNotNull(context,
            "Root context is null even though a context has been registered.");
        Assert.IsTrue(Object.ReferenceEquals(ctx, context),
            "Root context was not the same as the first context registered (it must be).");
    }

    [Test]
    public void RegisterNamedRootContext()
    {
        const string ctxName = "bingo";
        MockApplicationContext ctx = new MockApplicationContext(ctxName);
        ContextRegistry.RegisterContext(ctx);
        IApplicationContext rootContext = ContextRegistry.GetContext();
        Assert.IsNotNull(rootContext,
            "Root context is null even though a context has been registered.");
        Assert.AreEqual(ctxName, rootContext.Name,
            "Root context name is different even though the root context has been registered under the lookup name.");
    }

    [Test]
    public void RegisterNamedContext()
    {
        const string ctxName = "bingo";
        MockApplicationContext ctx = new MockApplicationContext(ctxName);
        ContextRegistry.RegisterContext(ctx);
        IApplicationContext context = ContextRegistry.GetContext(ctxName);
        Assert.IsNotNull(context,
            "Named context is null even though a context has been registered under the lookup name.");
        Assert.IsTrue(Object.ReferenceEquals(ctx, context),
            "Named context was not the same as the registered context (it must be).");
    }

    [Test]
    public void GetContextWithNullName()
    {
        Assert.Throws<ArgumentException>(() => ContextRegistry.GetContext(null));
    }

    [Test]
    public void GetContextWithEmptyName()
    {
        Assert.Throws<ArgumentException>(() => ContextRegistry.GetContext(""));
    }

    [Test]
    public void GetRootContextNotRegisteredThrowsException()
    {
        Assert.Throws<ApplicationContextException>(() =>
        {
            using (new HookableContextHandler.Guard(GetNullSection))
            {
                ContextRegistry.GetContext();
            }
        }, "No context registered. Use the 'RegisterContext' method or the 'spring/context' section from your configuration file.");
    }

    [Test]
    public void GetContextByNameNotRegisteredThrowsException()
    {
        Assert.Throws<ApplicationContextException>(
            () => ContextRegistry.GetContext("bingo"),
            "No context registered under name 'bingo'. Use the 'RegisterContext' method or the 'spring/context' section from your configuration file.");
    }

    [Test]
    public void CanBuildProxyForClassWithProtectedConstructor()
    {
        CompositionProxyTypeBuilder typeBuilder = new CompositionProxyTypeBuilder();
        typeBuilder.TargetType = typeof(ClassWithProtectedCtor);
        typeBuilder.BuildProxyType();
    }

    [Test]
    public void ClearWithDynamicProxies()
    {
        CompositionProxyTypeBuilder typeBuilder = new CompositionProxyTypeBuilder();
        typeBuilder.TargetType = typeof(TestObject);
        Type proxyType = typeBuilder.BuildProxyType();

        DefaultListableObjectFactory of = new DefaultListableObjectFactory();
        RootObjectDefinition od1 = new RootObjectDefinition(proxyType, false);
        od1.PropertyValues.Add("Name", "Bruno");
        of.RegisterObjectDefinition("testObject", od1);

        GenericApplicationContext ctx1 = new GenericApplicationContext(of);
        ContextRegistry.RegisterContext(ctx1);

        ITestObject to1 = ContextRegistry.GetContext().GetObject("testObject") as ITestObject;
        Assert.IsNotNull(to1);
        Assert.AreEqual("Bruno", to1.Name);

        DefaultListableObjectFactory of2 = new DefaultListableObjectFactory();
        RootObjectDefinition od2 = new RootObjectDefinition(proxyType, false);
        od2.PropertyValues.Add("Name", "Baia");
        of2.RegisterObjectDefinition("testObject", od2);
        GenericApplicationContext ctx2 = new GenericApplicationContext(of2);

        ContextRegistry.Clear();

        ITestObject to2 = ctx2.GetObject("testObject") as ITestObject;
        Assert.IsNotNull(to2);
        Assert.AreEqual("Baia", to2.Name);
    }

    // TODO : Add support for .NET 1.x
    [Test]
    public void ClearWithConfigurationSection()
    {
        IApplicationContext ctx1 = ContextRegistry.GetContext();
        ContextRegistry.Clear();
        IApplicationContext ctx2 = ContextRegistry.GetContext();

        Assert.AreNotSame(ctx1, ctx2);
    }

    [Test(Description = "SPRNET-105")]
    public void ChokesIfChildContextRegisteredUnderNameOfAnExistingContext()
    {
        MockApplicationContext original = new MockApplicationContext("original");
        ContextRegistry.RegisterContext(original);
        MockApplicationContext duplicate = new MockApplicationContext("original");
        Assert.Throws<ApplicationContextException>(() => ContextRegistry.RegisterContext(duplicate));
    }

    [Test]
    public void RemovesContextFromRegistryWhenContextCloses()
    {
        StaticApplicationContext appCtx = new StaticApplicationContext();
        appCtx.Name = "myCtx";
        ContextRegistry.RegisterContext(appCtx);
        Assert.IsTrue(ContextRegistry.IsContextRegistered(appCtx.Name));
        appCtx.Dispose();
        Assert.IsFalse(ContextRegistry.IsContextRegistered(appCtx.Name));
    }

    [TestFixture]
    public class WhenHierarchicalContextsAllHaveDefaultNames
    {
        private MockApplicationContext _parentContext;
        private MockApplicationContext _childContext;
        private MockApplicationContext _grandChildContext;
        private MockApplicationContext _greatGrandChildContext;

        private string _expectedParentName;
        private string _expectedChildName;
        private string _expectedGrandChildName;
        private string _expectedGreatGrandChildName;

        [OneTimeSetUp]
        public void InitializeAllTests()
        {
            _expectedParentName = AbstractApplicationContext.DefaultRootContextName;
            _expectedChildName = string.Format("{0}/{1}", _expectedParentName, AbstractApplicationContext.DefaultRootContextName);
            _expectedGrandChildName = string.Format("{0}/{1}/{2}", _expectedParentName, _expectedChildName, AbstractApplicationContext.DefaultRootContextName);
            _expectedGreatGrandChildName = string.Format("{0}/{1}/{2}/{3}", _expectedParentName, _expectedChildName, _expectedGrandChildName, AbstractApplicationContext.DefaultRootContextName);
        }

        [SetUp]
        public void Setup()
        {
            //ensure prior-registered contexts are removed
            ContextRegistry.Clear();

            _parentContext = new MockApplicationContext();
            _parentContext.MockName = "parent";

            _childContext = new MockApplicationContext(_parentContext);
            _childContext.MockName = "child";
            _childContext.ParentContext = _parentContext;

            _grandChildContext = new MockApplicationContext(_childContext);
            _grandChildContext.MockName = "grandchild";
            _grandChildContext.ParentContext = _childContext;

            _greatGrandChildContext = new MockApplicationContext(_grandChildContext);
            _greatGrandChildContext.MockName = "greatgrandchild";
            _greatGrandChildContext.ParentContext = _grandChildContext;
        }

        [Test]
        public void RegisterContext_ConstructsNestedPathBasedNames_IfRegisterdInHierarchicalOrder()
        {
            ContextRegistry.RegisterContext(_parentContext);
            ContextRegistry.RegisterContext(_childContext);
            ContextRegistry.RegisterContext(_grandChildContext);
            ContextRegistry.RegisterContext(_greatGrandChildContext);

            Assert.AreEqual(_expectedParentName, ContextRegistry.GetContext().Name);
            Assert.AreEqual(_expectedChildName, ContextRegistry.GetContext(_expectedChildName).Name);
            Assert.AreEqual(_expectedGrandChildName, ContextRegistry.GetContext(_expectedGrandChildName).Name);
            Assert.AreEqual(_expectedGreatGrandChildName, ContextRegistry.GetContext(_expectedGreatGrandChildName).Name);
        }

        [Test]
        public void RegisterContext_ConstructsNestedPathBasedNames_IfRegisteringAMixOfDefaultAndExplicitNamedContexts()
        {
            //modify the expected names for the decendent contexts for this one test
            string childContextInitialName = AbstractApplicationContext.DefaultRootContextName + "_CUSTOM";
            _expectedChildName = string.Format("{0}/{1}", _expectedParentName, childContextInitialName);
            _expectedGrandChildName = string.Format("{0}/{1}/{2}", _expectedParentName, _expectedChildName, AbstractApplicationContext.DefaultRootContextName);
            _expectedGreatGrandChildName = string.Format("{0}/{1}/{2}/{3}", _expectedParentName, _expectedChildName, _expectedGrandChildName, AbstractApplicationContext.DefaultRootContextName);

            //setup custom child instance for this one test
            _childContext = new MockApplicationContext(_expectedChildName);
            _childContext.MockName = "child";
            _childContext.ParentContext = _parentContext;
            _grandChildContext.ParentContext = _childContext;

            //register contexts in conflict with hierarchical order
            ContextRegistry.RegisterContext(_parentContext);
            ContextRegistry.RegisterContext(_childContext);
            ContextRegistry.RegisterContext(_grandChildContext);
            ContextRegistry.RegisterContext(_greatGrandChildContext);

            Assert.AreEqual(_expectedParentName, ContextRegistry.GetContext(_expectedParentName).Name);
            Assert.AreEqual(_expectedChildName, ContextRegistry.GetContext(_expectedChildName).Name);
            Assert.AreEqual(_expectedGrandChildName, ContextRegistry.GetContext(_expectedGrandChildName).Name);
            Assert.AreEqual(_expectedGreatGrandChildName, ContextRegistry.GetContext(_expectedGreatGrandChildName).Name);
        }
    }
}
